﻿//****************************************************************************
// Copyright (c) Microsoft Corporation. All rights reserved.
//****************************************************************************
using System;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
using Microsoft.FxCop.Sdk;

namespace Microsoft.FxCop.Rules.ASP.Security
{
    class ViewStateUserKeyShouldBeSet : BasePageIntrospectionRule
    {

        #region *** Fields ***
        private static Method m_viewstateuserkeyMethod = null;
        #endregion

        #region *** Constructor ***
        public ViewStateUserKeyShouldBeSet()
            : base("ViewStateUserKeyShouldBeSet")
        {
        }
        #endregion

        #region *** Methods ***

        #region *** BaseIntrospectionRule ***
        public override void BeforeAnalysis()
        {
            base.BeforeAnalysis();

            Init();
        }

        public override ProblemCollection Check(TypeNode type)
        {
            Method pageInitMethod;
            bool viewStateUserKeySet = false;

            if (!ShouldAnalyzeType(type))
                return null;

            if ((pageInitMethod = GetPage_InitMethod(type)) != null)
            {
                if ((viewStateUserKeySet = AnalyzePageInit(pageInitMethod)))
                {
                    return base.Problems;
                }
            }

            if ((pageInitMethod = GetOnInitMethod(type)) != null)
            {
                if ((viewStateUserKeySet = AnalyzePageInit(pageInitMethod)))
                {
                    return base.Problems;
                }
            }

            ReportIssue(type.Name.Name);

            return base.Problems;
        }
        #endregion

        #region *** Private ***
        private void Init()
        {
            m_viewstateuserkeyMethod = FrameworkTypes.Page.GetProperty(Identifier.For("ViewStateUserKey")).Setter;
        }

        protected override bool ShouldAnalyzeType(TypeNode type)
        {
            if (base.ShouldAnalyzeType(type))
            {
                return !type.FullName.EndsWith("_aspx");
            }
            else
            {
                return false;
            }
        }

        private static bool IsMVCViewPage(TypeNode type)
        {
            TypeNode baseType;

            baseType = type;

            while (true)
            {
                if (baseType == null)
                    break;

                //System.Web.Mvc.ViewPage
                if( baseType.FullName == "System.Web.Mvc" )
                        return true;

                if (baseType == FrameworkTypes.Object)
                    break;

                baseType = baseType.BaseType;
            }

            return false;
        }

        private Method GetPage_InitMethod(TypeNode type)
        {
            TypeNode baseType;
            Identifier paramId;
            Method method;
            TypeNode objectType = FrameworkAssemblies.Mscorlib.GetType(Identifier.For("System"), Identifier.For("Object"));
            TypeNode[] paramTypes = {   objectType,
                                        FrameworkAssemblies.Mscorlib.GetType(Identifier.For("System"), Identifier.For("EventArgs"))
                                    };

            paramId = Identifier.For("Page_Init");

            baseType = type;

            while (true)
            {
                if ((method = baseType.GetMethod(paramId, paramTypes)) != null)
                    return method;

                if (baseType == objectType)
                    break;

                baseType = baseType.BaseType;
            }

            return null;
        }

        private Method GetOnInitMethod(TypeNode type)
        {
            TypeNode baseType;
            Identifier paramId;
            Method method;
            TypeNode objectType = FrameworkAssemblies.Mscorlib.GetType(Identifier.For("System"), Identifier.For("Object"));
            TypeNode[] paramTypes = {
                                        FrameworkAssemblies.Mscorlib.GetType(Identifier.For("System"), Identifier.For("EventArgs"))
                                    };

            paramId = Identifier.For("OnInit");

            baseType = type;

            do
            {
                if ((method = baseType.GetMethod(paramId, paramTypes)) != null)
                    return method;

                baseType = baseType.BaseType;
            }
            while (baseType != objectType);

            return null;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="pageInitMethod"></param>
        /// <returns>true if the ViewStateUserKey is set and false if not</returns>
        private bool AnalyzePageInit(Method pageInitMethod)
        {
            Instruction currentInstruction;
            Method methodCalled;

            /*
             * We are looking for the set_ViewStateUserKey method call inside the
             * Page_Init implementation. If we can't find it we flag.
             */
            for (int instructionIndex = 0;
                instructionIndex < pageInitMethod.Instructions.Count;
                instructionIndex++)
            {
                currentInstruction = pageInitMethod.Instructions[instructionIndex];

                if (!IsMethodCall(currentInstruction))
                    continue;

                methodCalled = (Method)currentInstruction.Value;

                // the ViewStateUserKey is called
                if (methodCalled == m_viewstateuserkeyMethod)
                    return true;
            }

            // The call wasn't found and we need to flag
            return false;
        }

        private static bool IsMethodCall(Instruction instruction)
        {
            switch (instruction.OpCode)
            {
                case OpCode.Call:
                case OpCode.Calli:
                case OpCode.Callvirt:
                    return true;
                default:
                    return false;
            }
        }

        private void ReportIssue(string issueData)
        {
            Problem problem = new Problem(base.GetResolution(new string[] { issueData }));

            base.Problems.Add(problem);
        }
        #endregion

        #endregion

    }
}
